/*
   CLCore.cpp

   Created by Miroslav Karpis on 1/12/11.
   Copyright 2011 Miroslav Karpis. All rights reserved.

   This file is part of molecEvol.

   molecEvol is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Foobar is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "CLCore.h"

CLCore::CLCore()
{
}

//------
bool CLCore::getHostDeviceInfo()
{

    cout << "(CLCore) - checking OpenCL compatibility" << endl;

    char chBuffer[1024];

    // -----get platform information
    //first lets see that how many opencl platforms do we have
    if(clGetPlatformIDs(0, NULL, &num_platforms))
        return 0;


    if (num_platforms == 0) {
        cout << "No OpenCL platform found\n\n";
        return 0;
    }

    cout << "Number of available platforms:" << num_platforms << endl;

    //if there's a platform or more allocate space and get the IDs
    clPlatformIDs = new cl_platform_id[num_platforms];
    clGetPlatformIDs(num_platforms, clPlatformIDs, NULL);

    //displaying platform details
    for(cl_uint i = 0; i < num_platforms; ++i)
    {
        clGetPlatformInfo(clPlatformIDs[i], CL_PLATFORM_NAME, 1024, &chBuffer, NULL);
        cout << "\t Platform name: " << chBuffer << endl;
        clGetPlatformInfo(clPlatformIDs[i], CL_PLATFORM_PROFILE, 1024, &chBuffer, NULL);
        cout << "\t PROFILE: " << chBuffer << endl;
        clGetPlatformInfo(clPlatformIDs[i], CL_PLATFORM_VERSION, 1024, &chBuffer, NULL);
        cout << "\t VERSION: " << chBuffer << endl;
        clGetPlatformInfo(clPlatformIDs[i], CL_PLATFORM_VENDOR, 1024, &chBuffer, NULL);
        cout << "\t VENDOR: " << chBuffer << endl;
        clGetPlatformInfo(clPlatformIDs[i], CL_PLATFORM_EXTENSIONS, 1024, &chBuffer, NULL);
        cout << "\t EXTENSIONS: " << chBuffer << endl;
    }

    //------get device information
    cl_uint num_devices = 0;
    size_t extensionSize;
    bool bSharingSupported = false;

    //allocate space and get device IDs
    clGetDeviceIDs(clPlatformIDs[0], CL_DEVICE_TYPE_GPU, NULL, NULL, &num_devices);
    //devicesIDs = (cl_device_id*)malloc(num_devices * sizeof(cl_device_id));
    devicesIDs = new cl_device_id[num_devices];
    clGetDeviceIDs(clPlatformIDs[0], CL_DEVICE_TYPE_GPU, 1, devicesIDs, &num_devices);

    //displaying device details
    cout << num_platforms << " OpenCL device(s) found on platform:" << endl;
    for (unsigned int i=0; i<num_devices; i++)
    {
        cl_uint buf_uint;
        cl_uint numOfItemDims;
        cl_ulong buf_ulong;
        size_t buf_sizeT;
        size_t *pbuf_sizeT;
        cout << "\t ID: " << i << ":" << endl;
        clGetDeviceInfo(devicesIDs[i], CL_DEVICE_NAME, 1024, &chBuffer, NULL);
        cout << "\t DEVICE_NAME =" << chBuffer << endl;
        clGetDeviceInfo(devicesIDs[i], CL_DEVICE_VENDOR, 1024, &chBuffer, NULL);
        cout << "\t DEVICE_VENDOR = " << chBuffer << endl;
        clGetDeviceInfo(devicesIDs[i], CL_DEVICE_VERSION, 1024, &chBuffer, NULL);
        cout << "\t DEVICE_VERSION = " << chBuffer << endl;
        clGetDeviceInfo(devicesIDs[i], CL_DRIVER_VERSION, 1024, &chBuffer, NULL);
        cout << "\t DRIVER_VERSION = " << chBuffer << endl;
        clGetDeviceInfo(devicesIDs[i], CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(buf_uint), &buf_uint, NULL);
        cout << "\t DEVICE_MAX_COMPUTE_UNITS = " << buf_uint << endl;
        clGetDeviceInfo(devicesIDs[i], CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(buf_uint), &buf_uint, NULL);
        cout << "\t DEVICE_MAX_CLOCK_FREQUENCY = " << buf_uint << endl;
        clGetDeviceInfo(devicesIDs[i], CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
        cout << "\t DEVICE_GLOBAL_MEM_SIZE = " << buf_ulong << endl;
        clGetDeviceInfo(devicesIDs[i], CL_DEVICE_LOCAL_MEM_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
        cout << "\t CL_DEVICE_LOCAL_MEM_SIZ = " << buf_ulong << endl;

        clGetDeviceInfo(devicesIDs[i], CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(buf_sizeT), &buf_sizeT, NULL);
        cout << "\t CL_DEVICE_MAX_WORK_GROUP_SIZE = " << buf_sizeT << endl;
        clGetDeviceInfo(devicesIDs[i], CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, sizeof(buf_uint), &buf_uint, NULL);
        cout << "\t CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS = " << buf_uint << endl;
        numOfItemDims = buf_uint;
        pbuf_sizeT = (size_t*)malloc(numOfItemDims * sizeof(size_t));
        clGetDeviceInfo(devicesIDs[i], CL_DEVICE_MAX_WORK_ITEM_SIZES, numOfItemDims*sizeof(buf_sizeT), pbuf_sizeT, NULL);
        cout << "\t CL_DEVICE_MAX_WORK_ITEM_SIZES = ";
        for(uint16_t i=0; i<numOfItemDims ;i++)
            cout << pbuf_sizeT[i] << " ";
        cout << endl;
        clGetDeviceInfo(devicesIDs[i], CL_DEVICE_ADDRESS_BITS, sizeof(buf_uint), &buf_uint, NULL);
        cout << "\t CL_DEVICE_ADDRESS_BITS = " << buf_uint << endl;

    }

    return 1;
}

//-----
cl_context CLCore::createContext()
{
    //create context
    cl_context context;
    context = 0;
    cl_context_properties contextProperties[] =
    {
        CL_CONTEXT_PLATFORM,
        (cl_context_properties)clPlatformIDs[0],
        0
    };
    context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_GPU,
                                      NULL, NULL, &errCode);
    if (errCode != CL_SUCCESS)
    {
        cout << "Could not create GPU context, trying CPU..." << endl;
        context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_CPU,
                                          NULL, NULL, &errCode);

        //TODO this needs to have proper error handling
        if (errCode != CL_SUCCESS)
            cerr << "Failed to create an OpenCL GPU or CPU context." << endl;
    }

    return context;
}

//------
cl_program CLCore::CreateProgram(cl_context context,
                                 cl_device_id device,
                                 const char* fileName)
{
    cl_int errNum;
    cl_program program;

    std::ifstream kernelFile(fileName, std::ios::in);
    if (!kernelFile.is_open())
    {
        std::cerr << "Failed to open file for reading: " << fileName << std::endl;
        return NULL;
    }

    std::ostringstream oss;
    oss << kernelFile.rdbuf();

    std::string srcStdStr = oss.str();
    const char *srcStr = srcStdStr.c_str();
    program = clCreateProgramWithSource(context, 1,
                                        (const char**)&srcStr,
                                        NULL, NULL);
    if (program == NULL)
    {
        std::cerr << "Failed to create CL program from source." << std::endl;
        return NULL;
    }

    errNum = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
    if (errNum != CL_SUCCESS)
    {
        // Determine the reason for the error
        char buildLog[16384];
        clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG,
                              sizeof(buildLog), buildLog, NULL);

        std::cerr << "Error in kernel: " << std::endl;
        std::cerr << buildLog;
        clReleaseProgram(program);
        return NULL;
    }

    return program;
}
